#include "executePlan.h"

#ifdef TEST_PLAN
#define PRIVATE 
#else
#define PRIVATE static
#endif

#define OPERATION(type) (type == PLAN_FILTER ? "filter" : \
                         type == PLAN_PROJECT ? "project" : \
                         type == PLAN_SCAN ? "scan" : \
                         type == PLAN_JOIN ? "join" : \
                         type == PLAN_INSERT ? "insert" : \
                         type == PLAN_UPDATE ? "update" : \
                         type == PLAN_DELETE ? "delete" : "unknown")

/* PRIVATE function declaration start */

PRIVATE PlanNode *createPlanNode(PlanNodeType type, int value_num, int flag);
PRIVATE void addChild(PlanNode *parent, PlanNode *child);

PRIVATE PlanNode *stmtSelectToPlan(ASTNode *stmt);
PRIVATE PlanNode *stmtUpdateToPlan(ASTNode *stmt);
PRIVATE PlanNode *stmtInsertToPlan(ASTNode *stmt);
PRIVATE PlanNode *stmtDeleteToPlan(ASTNode *stmt);

PRIVATE PlanNode *selectToPlan(ASTNode *select);
PRIVATE PlanNode *updateToPlan(ASTNode *update);
PRIVATE PlanNode *valuesToPlan(ASTNode *insert);
PRIVATE PlanNode *fromToPlan(ASTNode *from);
PRIVATE PlanNode *whereToPlan(ASTNode *where);
PRIVATE PlanNode *setToPlan(ASTNode *set);
PRIVATE PlanNode *joinToPlan(ASTNode *join);
PRIVATE PlanNode *intoToPlan(ASTNode *into);

#define PLAN_NODE_CONNECT(p, s, j, f) \
    if (j == NULL) { \
        if (f != NULL) { \
            addChild(s, f); \
            addChild(f, p); \
        } else { \
            addChild(s, p); \
        } \
    } else { \
        if (f != NULL) { \
            addChild(s, j); \
            addChild(j, f); \
            addChild(f, p); \
        } else { \
            addChild(s, j); \
            addChild(j, p); \
        } \
    } \

/* PRIVATE function declaration start */

PlanNode *buildPlanTree(ASTNode *ast)
{
    if (ast == NULL || ast->children[0] == NULL) return NULL;

    ASTNode *stmt = ast->children[0];

    switch (stmt->type) {
        case AST_STMT_SELECT:
            return stmtSelectToPlan(stmt);
        case AST_STMT_UPDATE:
            return stmtUpdateToPlan(stmt);
        case AST_STMT_DELETE:
            return stmtDeleteToPlan(stmt);
        case AST_STMT_INSERT:
            return stmtInsertToPlan(stmt);
        default:
            return NULL;
    }
}

void printPlan(PlanNode *root)
{
    if (root == NULL) return;

    printf("Operation: %-8s, Value: ", OPERATION(root->type));
    if (root->type != PLAN_FILTER && root->type != PLAN_UPDATE) {
        for (int i = 0; i < root->value_list->count; i++) {
            printf("%s ", root->value_list->value[i]);
        }
    } else {
        for (int i = 0; i < root->expr_list->count; i++) {
            printf("%s%s%s%c", root->expr_list->column[i],
                               root->expr_list->operator[i],
                               root->expr_list->value[i], 
                               root->expr_list->seperator[i]);
        }
    }
    printf("\n");

    if (root->children != NULL) {
        for (int i = 0; i < root->child_count; i++) {
            printPlan(root->children[i]);
        }
    }
}

void destroyPlanTree(PlanNode *root)
{
    if (root == NULL) return;

    if (root->children != NULL) {
        for (int i = 0; i < root->child_count; i++) {
            destroyPlanTree(root->children[i]);
        }
    }

    if (root->type != PLAN_FILTER) {
        for (int i = 0; i < root->value_list->count; i++) {
            free(root->value_list->value[i]);
            root->value_list->value[i] = NULL;
        }
        free(root->value_list);
        root->value_list = NULL;
    } else {
        for (int i = 0; i < root->expr_list->count; i++) {
            free(root->expr_list->column[i]);
            free(root->expr_list->operator[i]);
            free(root->expr_list->value[i]);

            root->expr_list->column[i] = NULL;
            root->expr_list->operator[i] = NULL;
            root->expr_list->value[i] = NULL;
        }
        free(root->expr_list);
        root->expr_list = NULL;
    }

    free(root);
    root = NULL;
}

/* PRIVATE function implemention start */

PRIVATE PlanNode *stmtSelectToPlan(ASTNode *stmt)
{
    PlanNode *project_node = NULL;
    PlanNode *scan_node = NULL;
    PlanNode *join_node = NULL;
    PlanNode *filter_node = NULL;

    for (int i = 0; i < stmt->child_count; i++) {
        switch (stmt->children[i]->type) {
            case AST_CLAUSE_SELECT:
                ASTNode *select = stmt->children[i];
                project_node = selectToPlan(select);
                break;
                
            case AST_CLAUSE_FROM:
                ASTNode *from = stmt->children[i];
                scan_node = fromToPlan(from);
                break;

            case AST_CLAUSE_JOIN:
                ASTNode *join = stmt->children[i];
                join_node = joinToPlan(join);
                break;

            case AST_CLAUSE_WHERE:
                ASTNode *where = stmt->children[i];
                filter_node = whereToPlan(where);
                break;

            default:
                return NULL;
        }
    }

    PLAN_NODE_CONNECT(project_node, scan_node, join_node, filter_node);

    return scan_node;
}

PRIVATE PlanNode *createPlanNode(PlanNodeType type, int value_num, int flag)
{
    if (type == PLAN_UNKNOWN) {
        fprintf(stderr, "Error: Plan node is unknown.\n");
        exit(EXIT_FAILURE);
    }  

    PlanNode *node = (PlanNode *) malloc(sizeof(PlanNode));
    if (node == NULL) {
        fprintf(stderr, "Error: Create Plan node.\n");
        exit(EXIT_FAILURE);
    }

    node->type = type;
    if (flag == 0) {
        node->value_list = (valueList *) malloc(sizeof(valueList));
        node->value_list->count = value_num;
        for (int i = 0; i < node->value_list->count; i++) {
            node->value_list->value[i] = (char *) malloc(MAX_VALUE_SIZE);
        }
    } else {
        node->expr_list = (exprList *) malloc(sizeof(exprList));
        node->expr_list->count = value_num;
        for (int i = 0; i < node->expr_list->count; i++) {
            node->expr_list->column[i] = (char *) malloc(MAX_TOKEN_LEN);
            node->expr_list->operator[i] = (char *) malloc(MAX_TOKEN_LEN);
            node->expr_list->value[i] = (char *) malloc(MAX_TOKEN_LEN);
        }
    }
    node->child_count = 0;
    for (int i = 0; i < MAX_PLAN_NODES; i++) {
        node->children[i] = NULL;
    }

    return node;
}

PRIVATE void addChild(PlanNode *parent, PlanNode *child)
{
    if (parent->child_count < MAX_PLAN_NODES) 
        parent->children[parent->child_count++] = child;
}

PRIVATE PlanNode *stmtUpdateToPlan(ASTNode *stmt)
{
    PlanNode *update_node = NULL;
    PlanNode *scan_node = NULL;
    PlanNode *join_node = NULL;
    PlanNode *filter_node = NULL;

    for (int i = 0; i < stmt->child_count; i++) {
        switch (stmt->children[i]->type) {
            case AST_CLAUSE_UPDATE:
                ASTNode *update = stmt->children[i];
                scan_node = updateToPlan(update);
                break;

            case AST_CLAUSE_SET: 
                ASTNode *set = stmt->children[i];
                update_node = setToPlan(set);
                break;

            case AST_CLAUSE_JOIN:
                ASTNode *join = stmt->children[i];
                join_node = joinToPlan(join);
                break;

            case AST_CLAUSE_WHERE:
                ASTNode *where = stmt->children[i];
                filter_node = whereToPlan(where);
                break;

            default:
                return NULL;
        }
    }

    PLAN_NODE_CONNECT(update_node, scan_node, join_node, filter_node);

    return scan_node;
}

PRIVATE PlanNode *stmtInsertToPlan(ASTNode *stmt)
{
    PlanNode *insert_node = NULL;
    PlanNode *scan_node = NULL;

    for (int i = 0; i < stmt->child_count; i++) {
        switch (stmt->children[i]->type) {
            case AST_CLAUSE_INSERT:
                break;

            case AST_CLAUSE_INTO:
                ASTNode *into = stmt->children[i];
                scan_node = intoToPlan(into);
                break;

            case AST_CLAUSE_VALUES:
                ASTNode *values = stmt->children[i];
                insert_node = valuesToPlan(values);
                break;

            default:
                return NULL;
        }
    }

    addChild(scan_node, insert_node);

    return scan_node;
}

PRIVATE PlanNode *stmtDeleteToPlan(ASTNode *stmt)
{
    PlanNode *delete_node = NULL;
    PlanNode *filter_node = NULL;
    PlanNode *scan_node = NULL;
    PlanNode *join_node = NULL;

    for (int i = 0; i < stmt->child_count; i++) {
        switch (stmt->children[i]->type) {
            case AST_CLAUSE_DELETE:
                delete_node = createPlanNode(PLAN_DELETE, 0, 0);
                break;

            case AST_CLAUSE_FROM:
                ASTNode *from = stmt->children[i];
                scan_node = fromToPlan(from);
                break;

            case AST_CLAUSE_JOIN:
                ASTNode *join = stmt->children[i];
                join_node = joinToPlan(join);
                break;

            case AST_CLAUSE_WHERE:
                ASTNode *where = stmt->children[i];
                filter_node = whereToPlan(where);
                break;

            default:
                return NULL;
        }
    }

    PLAN_NODE_CONNECT(delete_node, scan_node, join_node, filter_node);

    return scan_node;
}

PRIVATE PlanNode *fromToPlan(ASTNode *from)
{
    ASTNode *table_list = from->children[0];
    PlanNode *scan_node = createPlanNode(PLAN_SCAN, table_list->child_count, 0);

    for (int k = 0; k < table_list->child_count; k++) {
        strncpy(scan_node->value_list->value[k], table_list->children[k]->value, MAX_TOKEN_LEN);
    }

    return scan_node;
}

PRIVATE PlanNode *whereToPlan(ASTNode *where)
{
    ASTNode *condition = where->children[0];
    ASTNode *or = condition->children[0];

    int expr_num = 0;
    for (int j = 0; j < or->child_count; j++) {
        ASTNode *and = or->children[j];
        expr_num += and->child_count;
    } 
    PlanNode *filter_node = createPlanNode(PLAN_FILTER, expr_num, 1);

    int count= 0;
    for (int k = 0; k < or->child_count; k++) {
        ASTNode *and = or->children[k];
        for (int m = 0; m < and->child_count; m++) {
            ASTNode *expr = and->children[m];
            strncpy(filter_node->expr_list->column[count], expr->children[0]->value, MAX_TOKEN_LEN);
            strncpy(filter_node->expr_list->operator[count], expr->children[1]->value, MAX_TOKEN_LEN);
            strncpy(filter_node->expr_list->value[count], expr->children[2]->value, MAX_TOKEN_LEN);
            filter_node->expr_list->seperator[count] = ',';
            count++;
        }
        filter_node->expr_list->seperator[count - 1] = ':';
    }
    filter_node->expr_list->seperator[count - 1] = '\0';

    return filter_node;
}

PRIVATE PlanNode *setToPlan(ASTNode *set)
{
    ASTNode *expr_list = set->children[0];
    PlanNode *update_node = createPlanNode(PLAN_UPDATE, expr_list->child_count, 1);

    for (int k = 0; k < expr_list->child_count; k++) {
        ASTNode *expr = expr_list->children[k];
        strncpy(update_node->expr_list->column[k], expr->children[0]->value, MAX_TOKEN_LEN);
        strncpy(update_node->expr_list->operator[k], expr->children[1]->value, MAX_TOKEN_LEN);
        strncpy(update_node->expr_list->value[k], expr->children[2]->value, MAX_TOKEN_LEN);
        update_node->expr_list->seperator[k] = ',';
    }
    update_node->expr_list->seperator[expr_list->child_count - 1] = '\0';

    return update_node;
}

PRIVATE PlanNode *joinToPlan(ASTNode *join)
{
    ASTNode *table_list = join->children[0];
    PlanNode *join_node = createPlanNode(PLAN_JOIN, table_list->child_count, 0);

    for (int k = 0; k < table_list->child_count; k++) {
        strncpy(join_node->value_list->value[k], table_list->children[k]->value, MAX_TOKEN_LEN);
    }

    return join_node;
}

PRIVATE PlanNode *selectToPlan(ASTNode *select)
{
    ASTNode *column_list = select->children[0];
    PlanNode *project_node = createPlanNode(PLAN_PROJECT, column_list->child_count, 0);

    for (int k = 0; k < column_list->child_count; k++) {
        strncpy(project_node->value_list->value[k], column_list->children[k]->value, MAX_TOKEN_LEN);
    }

    return project_node;
}

PRIVATE PlanNode *updateToPlan(ASTNode *update)
{
    return fromToPlan(update);
}

PRIVATE PlanNode *valuesToPlan(ASTNode *values)
{
    ASTNode *record_list = values->children[0];
    PlanNode *insert_node = createPlanNode(PLAN_INSERT, record_list->child_count, 0);

    for (int k = 0; k < record_list->child_count; k++) {
        strncpy(insert_node->value_list->value[k], record_list->children[k]->value, MAX_VALUE_SIZE);
    }

    return insert_node;
}

PRIVATE PlanNode *intoToPlan(ASTNode *into)
{
    return fromToPlan(into);
}

/* PRIVATE function implemention start */